Skip to content

fix(spec): the preset-comparand entry puts each carrier under the door that refuses it - #19801

Merged
os-support-ai merged 6 commits into
mainfrom
claude/issue-19778-preset-entry-carriers
Sep 23, 2026
Merged

os-support-ai merged 6 commits into
mainfrom
claude/issue-19778-preset-entry-carriers

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #19778

Clause-②: no

The shipped ADR-0087 semantic entry filter-preset-ordering-comparand-refused listed "page filter, component filter" among the FilterConditionSchema carriers. It also said "the schema door and the @objectstack/lint filter-preset-comparand rule refuse it at publish". Page and component filters are ViewFilterRuleSchema rule arrays, and no schema door judges a preset there. So an upgrader who swept stored pages with a schema parse found nothing and read the sweep as clean.

This PR rewrites surface, reason and acceptanceCriteria as a three-way split, with each carrier named by its declared type and its key. It also narrows two more sentences that are false today (see below).

No behaviour moves. No schema, accept set or lint rule is touched, and no export is added, removed or retyped. Every changed line in registry.ts is a string literal inside this one step-18 entry, which the exported MIGRATIONS_BY_MAJOR carries, so what moves in dist is prose. packages/lint is not touched; the lint's reach gap is #19791.

What the entry now says

The groups list the carriers measured, not a closed partition. The grep sentence at the head of acceptanceCriteria is the catch-all, and it now also names a gt/gte/lt/lte lookup filter value.

  1. Slots typed FilterConditionSchema are refused at parse, at the comparand's own path, and the lint rule reports them too. The slots are:

    • a dashboard widget filter;
    • a dashboard global-filter options-source filter (optionsFrom.filter);
    • a dataset filter and a dataset measure filter;
    • a report runtimeFilter, on the report or on a joined-report block;
    • a rollup summaryOperations.filter;
    • a relatedListFilter.
  2. Filters under a key the lint walks, whose declared type carries no preset check, parse GREEN, and the lint rule is the only door. These are:

    • ViewFilterRuleSchema rule arrays: a view's filter, a page element's dataSource.filter and a page component's filter prop;
    • a Mongo-shape record typed as a loose record rather than FilterConditionSchema: a flow CRUD node's config.filter.

    The lint is also what refuses a preset in an ordering [field, op, value] triple.

  3. Filters under a key the lint does not walk parse GREEN and lint GREEN, so neither door refuses them at publish. These are a page's interfaceConfig.filterBy rule array, and a lookup field's lookupFilters, whose ordering operators are spelled gt / gte / lt / lte.

    • acceptanceCriteria says the sweep is mechanical for groups 1 and 2 (os validate / os lint, plus a safeParse of the declaring schema for group 1), and by hand for group 3.
    • The hand sweep is one search per carrier:
      • a filterBy rule whose operator is an ordering one (greater_than, greater_than_or_equal, less_than, less_than_or_equal, before, after, between, or an alias);
      • a lookupFilters entry whose operator is gt, gte, lt or lte. These are the only ordering spellings that key accepts, and it has no between.
    • The windows come from DATE_RANGE_PRESET_MACRO_WINDOWS.

The prose names no refinement function, no issue or PR number beyond the entry's existing ruling citations, and says nothing about object-grid defaultFilters.

Two more sentences that were false, each changed alone

  • replacement: "... dashboard date-filter positions ..., which is the only place any layer ever resolved them". An analytics query's timeDimensions[].dateRange also accepts the preset names and resolves them. packages/core/src/utils/analytics-date-range.ts lowers them to a window.
    • Measured: AnalyticsQuerySchema with dateRange: 'last_30_days' is GREEN, and the control "Last 7 days" is refused at timeDimensions.0.dateRange.
    • The sentence now names both positions. The rest of replacement is unchanged.
  • reason: "Ordering positions only, deliberately: equality and membership are NOT judged". This is true of the schema door only. The lint rule's field-typed arm refuses a preset in an equality or membership position, in a filter its walk reaches, on a field it can resolve to a declared date or datetime. Where the filter binds to no object, or the field resolves to nothing, that arm cannot fire (see the binding rows below). The sentence now says which door is ordering-only, and what the arm needs.
  • The ruling citation, the "no D2 conversion" disposition and every other sentence are unchanged.

Probe matrix: the prose against every cell

Everything was parsed against the built dist, resolved through package exports: @objectstack/spec/ui and /data resolve to packages/spec/dist/{ui,data}/index.js, and @objectstack/lint to packages/lint/dist/index.cjs.

  • The rule is { field: 'close_date', operator: OP, value: V } on a declared date field.
  • The preset V is 'last_30_days', or ['today', '2026-12-31'] for between.
  • The dark control is '2026-01-01' (or an ISO pair). It is GREEN in every cell of every row, so that column is omitted.
group carrier: declared type and key schema parse, preset @objectstack/lint, preset
1 DashboardWidgetSchema.filter REFUSED filter.close_date.$gt REFUSED dashboards[0].widgets[0].filter.close_date.$gt
1 GlobalFilterOptionsFromSchema.filter REFUSED filter.close_date.$gt REFUSED dashboards[0].globalFilters[0].optionsFrom.filter.close_date.$gt
1 DatasetSchema.filter REFUSED filter.close_date.$gt REFUSED datasets[0].filter.close_date.$gt
1 DatasetMeasureSchema.filter REFUSED filter.close_date.$gt REFUSED datasets[0].measures[0].filter.close_date.$gt
1 ReportSchema.runtimeFilter REFUSED runtimeFilter.close_date.$gt REFUSED reports[0].runtimeFilter.close_date.$gt
1 JoinedReportBlockSchema.runtimeFilter REFUSED runtimeFilter.close_date.$gt REFUSED reports[0].blocks[0].runtimeFilter.close_date.$gt
1 FieldSchema.relatedListFilter REFUSED relatedListFilter.close_date.$gt REFUSED objects[1].fields.parent.relatedListFilter.close_date.$gt
1 FieldSchema.summaryOperations.filter REFUSED summaryOperations.filter.close_date.$gt REFUSED objects[1].fields.total.summaryOperations.filter.close_date.$gt
2 ListViewSchema.filter, greater_than / between GREEN / GREEN REFUSED views[0].filter[0].value / .value[0]
2 PageSchema element dataSource.filter, both ops GREEN / GREEN REFUSED pages[0].regions[0].components[0].dataSource.filter[0].value / .value[0]
2 record:related_list filter prop (its props schema, and PageSchema), both ops GREEN, GREEN / GREEN, GREEN REFUSED ...components[0].properties.filter[0].value / .value[0]
2 the same prop authored as a triple ['close_date', '>=', V] GREEN (PageSchema) REFUSED ...properties.filter[2]
3 PageSchema interfaceConfig.filterBy, both ops GREEN / GREEN GREEN / GREEN (0 findings)

Controls on group 3, with the same rule:

  • The slot is parsed. A filterBy value { $x: 1 } is REFUSED at interfaceConfig.filterBy.0.value.
  • The key name decides. The same rule under interfaceConfig.filter is refused by the lint at pages[0].interfaceConfig.filter[0].value, while PageSchema refuses the alias key.

The eq rows back the narrowed reason sentence:

  • FilterConditionSchema { close_date: 'last_30_days' } and { close_date: { $in: ['last_30_days'] } } are both GREEN.
  • A widget filter { close_date: 'last_30_days' } is refused by the lint at dashboards[0].widgets[0].filter.close_date.
  • A view rule equals preset is refused by the lint at views[0].filter[0].value, and a view rule in preset at views[0].filter[0].value[0].
  • On a select field, this_quarter stays GREEN for both equals and in.

The replacement sentence checks:

  • DashboardSchema dateRange.defaultRange: 'last_30_days' is GREEN, and the control 'last_60_days' is refused at dateRange.defaultRange.
  • DATE_RANGE_PRESET_MACRO_WINDOWS is exported from @objectstack/spec/data with 13 keys.

Rows to append to the matrix table (group column first):

group carrier: declared type and key schema parse, preset @objectstack/lint, preset
2 flow get_record / update_record / delete_record node config.filter (a loose record), $gt and $between GREEN (FlowSchema; GetRecordConfigSchema too) REFUSED flows[0].nodes[1].config.filter.close_date.$gt / .$between[0]
3 FieldSchema.lookupFilters, operator gt / gte / lt / lte GREEN GREEN (0 findings)

Both new rows read GREEN / GREEN with the ISO dark control.

The lookupFilters operator vocabulary, each with value 'last_30_days':

  • GREEN at FieldSchema: gt, gte, lt and lte, plus the non-ordering eq, ne, in, notIn and contains.
  • REFUSED at lookupFilters.0.operator: greater_than, greater_than_or_equal, less_than, after, before, between, >, >=, $gt and GT.
  • The lint is GREEN on every spelling.
  • Key-name control: the same { field, operator: 'gt', value: 'last_30_days' } under a view's filter is refused by the lint at views[0].filter[0].value.

Binding rows, which back the tightened reason sentence:

  • A widget whose dataset binds to deal with { close_date: 'last_30_days' } is REFUSED at dashboards[0].widgets[0].filter.close_date.
  • The same widget with dataset: 'ghost', which resolves to nothing, is GREEN.
  • A bound widget with an undeclared field, { ghost_date: 'last_30_days' }, is GREEN.
  • A bound select field with this_quarter is GREEN.
  • An unbound widget with an ordering { close_date: { $gt: 'last_30_days' } } is REFUSED, because arm 1 is field-agnostic.

True on both trees (replaces the round-2 paragraph). The whole matrix, the eq and replacement rows, and the rows above were run on two builds:

The outputs are byte-identical, and the round-2 matrix output is unchanged.

Publishing reach

src/migrations/entries/** is generator input. registry.ts is what dist is built from, and it was regenerated with pnpm --filter @objectstack/spec gen:migration-registry, never hand-edited.

Counts use grep -o -F | wc -l over dist/index.js, dist/index.mjs, dist/browser/index.js and dist/browser/index.mjs. "Before" is base 2cf9db7c43. For the new sentences, it is #19750's a17615e3ad build, whose copy of this entry is byte-identical to base's (git diff --quiet exit 0); registry.ts at base holds 0 of them.

string before after
page filter, component filter, rollup filter (removed) 4 0
the schema door and the @objectstack/lint filter-preset-comparand rule refuse it at publish (removed) 4 0
report each one by path, so the sweep is mechanical (removed) 4 0
which is the only place any layer ever resolved them (removed) 4 0
Ordering positions only, deliberately: equality and membership are NOT judged (removed) 4 0
is a rule array under a key the lint does NOT walk: it parses GREEN and lints GREEN, so neither door refuses it at publish (new) 0 4
is two doors with different reach, not one: the FilterConditionSchema parse refuses the shape on the slots typed that way (new) 0 4
Group (3) is BY HAND, because nothing reports it (new) 0 4
stay fully legal where a layer resolves them to a window (new) 0 4
Ordering positions only at the schema door, deliberately: it judges no equality (new) 0 4
compared false against every row: HTTP 200 (dark control, unchanged sentence) 4 4
is a rule array under a key the lint DOES walk (zero control) 0 0

Round 3. Counted with the same four bundles. "Before" is the build at 775de57ad9, and "after" is the build at bff19cc8b9.

string before after
and by its key, in three groups (removed) 4 0
is a rule array under a key the lint does NOT walk: it parses GREEN and lints GREEN, so neither door refuses it at publish (removed) 4 0
which makes it the only door for a ViewFilterRuleSchema rule array (removed) 4 0
because nothing reports it: search every (removed) 4 0
position on a declared date or datetime field, and on a temporal field (removed) 4 0
a list of what was measured, not a closed partition (new) 0 4
a Mongo-shape filter record typed as a loose record rather than FilterConditionSchema (new) 0 4
whose ordering operators are spelled gt / gte / lt / lte (new) 0 4
the only ordering spellings that key accepts (new) 0 4
on a field it can resolve to a declared date or datetime (new) 0 4
compared false against every row: HTTP 200 (dark control) 4 4
a list of what was measured, and a closed partition (zero control) 0 0

Cumulative against base 2cf9db7c43:

  • The five round-2 removed strings still read 0.
  • Four of round 2's five new sentences still read 4. The fifth, the does NOT walk sentence, was reworded this round and is counted as removed above.
  • The changeset's reach bullet now says nine sentences.

Verification

Head is a82423396b. Every exit code was captured before any pipe.

  • pnpm --filter @objectstack/spec check:migration-registry → exit 0, "current (232 semantic, 207 retired-key, 183 retired-def)". Every changed line in registry.ts is a string literal inside this entry.
  • pnpm --filter @objectstack/spec check:generated → exit 0, "All 15 generated artifacts are up to date", with a declaration stamp match.
  • pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2 → exit 0: 517 test files passed (517); 15091 tests passed, 1 todo.
  • pnpm --filter @objectstack/spec typecheck → exit 0, check:test-typecheck: OK.
  • Gates derived with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands from this worktree: 80 derived. On --ran reconciliation: 79 run, exit 0; 1 NOT MEASURED; 0 UNRUN.
    • The NOT MEASURED one is pnpm check:dual-build-cjs-loads, exit 3 PREREQUISITE NOT MET. It needs a repo-wide build, which is CI's run. ⛔ It is not a pass.
    • pnpm check:lean-entry-closure exited 0 after the @objectstack/objectql closure was built.
  • The derivation warned STALE TREE: the branch is behind origin/main de4ed33fd5, and one gate file changed across that range (scripts/check-spec-docblock-symbol-anchors.mjs).
    • That gate and its --self-test were re-run from main's copy on the merged generation, and both exit 0. The merged generation is a throwaway worktree at an unpushed merge commit of tree a409220cc1, built with git merge-tree --write-tree HEAD origin/main.
    • The same merged tree derives the identical 80 families, with no stale warning.
    • The branch is not merged: no conflict forces it.
  • The five artifact-roster gates whose roster sits under a touched directory all exit 0: check-changeset-fixed.mjs, check:meta-url-spelling, check:authz-resolver, check:error-code-casing, check:filter-alias-parity.
  • A control-byte self-scan of the three changed files finds 0 (grep exit 1).
  • Narrowed eslint on the two changed .ts files reports 2 files, 0 errors, 0 warnings.
    • The population is the flat config's ts/js object.
    • --print-config shows no parserOptions.project or projectService, so linting is not type-aware.
    • The repo-wide pnpm lint is CI's.
  • CI at a82423396b, on a single read: 35 check-runs, 32 success, 3 skipped, 0 failure.

Acceptance notes


Generated by Claude Code


Generated by Claude Code

…r that refuses it

The step-18 semantic entry filter-preset-ordering-comparand-refused listed
page and component filters among the FilterConditionSchema carriers the
schema door refuses. They are ViewFilterRuleSchema rule arrays: the lint is
the only door there, and a page's interfaceConfig.filterBy is walked by
neither. surface, reason and acceptanceCriteria now draw the three-way
split; the replacement's "only place any layer ever resolved them" and the
reason's "equality and membership are NOT judged" are narrowed to what is
true today.

Claude-Session: https://claude.ai/code/session_013RDBh5DqXd2xnLwvHLgLFr
Co-authored-by: Claude <noreply@anthropic.com>
Output of `pnpm --filter @objectstack/spec gen:migration-registry`, never
hand-edited; every changed line is a string literal inside step 18's
filter-preset-ordering-comparand-refused entry.

Claude-Session: https://claude.ai/code/session_013RDBh5DqXd2xnLwvHLgLFr
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tooling labels Sep 23, 2026
@github-actions

github-actions Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

What this run could not see
  • 4 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 60 of 215 client-bound route-ledger rows — the other 155 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 155: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 100 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 136 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 0b83e01627dbf2dce05cb38148fecd3cff430696 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 3c00e296960792c140150f6619501525df73fbda — the merge of head a82423396b1c204e2d8f6a1ae76377d9d20ea736 into base 0b83e01627dbf2dce05cb38148fecd3cff430696, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 3c00e296960792c140150f6619501525df73fbda && git checkout 3c00e296960792c140150f6619501525df73fbda
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 0b83e01627dbf2dce05cb38148fecd3cff430696 a82423396b1c204e2d8f6a1ae76377d9d20ea736 && git checkout -B drift-repro 0b83e01627dbf2dce05cb38148fecd3cff430696 && git merge --no-ff a82423396b1c204e2d8f6a1ae76377d9d20ea736

node scripts/docs-audit/affected-docs.mjs --json 0b83e01627dbf2dce05cb38148fecd3cff430696

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Copy link
Copy Markdown
Collaborator

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 775de57ad957923ced730dfefb764ef7fd7b1be6

Card #19778, round 2. Base main, merge-base 2cf9db7c43. Reading moment: 2026-09-23T06:40Z. A record binds ONE head and one reading moment; it is ⛔ not a standing statement about CI.

Tier, measured by the SEAT from the reviewer's transcript (⛔ not its self-report): CONTRACT_REVIEW_TIER on origin/main is claude-fable-5-1. Governing assistant-request stamps 114 / 114 claude-fable-5-1; superset "model" + "advisorModel" 250 / 250; dark control empty. The fallback/overload sweep, read in context, is tool-schema text and repo source — ⛔ none a notice. ⇒ AT TIER. This seat is off-tier and ⛔ did not 自审.

① Derived judgments

  • Every changed sentence is true on both trees. The reviewer built dist for this head and for fix(spec)!: the filter doors refuse the three shapes they already declared refused (#19514) #19750's head 52f6196199, then ran one probe matrix against both. The outputs differ only on two tree-control rows, which proves each run read its own dist. Every dark control is GREEN in every cell.
  • Group (1) holds. All 8 named FilterConditionSchema slots were probed with $gt and $between. Each is REFUSED at parse at the comparand's own path, AND refused by the lint.
  • Group (2) holds. ListViewSchema.filter, a page element's dataSource.filter and the record:related_list filter prop parse GREEN and are refused by the lint. A triple in the prop is refused at …filter[2].
  • Group (3) holds. interfaceConfig.filterBy parses GREEN. validatePresetComparands and every authoring rule, under both lint and validate, report nothing on a filterBy path. Controls: a malformed filterBy value is refused, the same rule under interfaceConfig.filter is lint-refused, and a page carrying both carriers draws exactly one finding.
  • The two extra sentences hold. In replacement, timeDimensions[].dateRange accepts last_30_days and refuses 'Last 7 days', and the resolver exists. In reason, the schema door judges no equality or membership position, while the lint refuses those positions on a bound date field.
  • Nothing else moved. The sentence-level diff keeps the ruling citation and the no-D2 disposition verbatim. id is identical and the key set is unchanged. No tracker number was added. packages/lint is untouched.

② Semver level

patch on @objectstack/spec is right: the prose reaches all four shipped bundles. The 5 removed strings go 1 → 0 per bundle and the 6 new unique sentences go 0 → 1 per bundle. The dark control holds at 1, and the zero control holds at 0. The registry is byte-exact generator output: regenerated sha256 unchanged, check:migration-registry exit 0 (232 / 207 / 183), and all four diff hunks are string literals inside this entry.

③ Boundary flags

None tripped. Exactly three files. Fixes #19778, line-leading Clause-②: no, model-free trailers (check-commit-card-trailers exit 0). No governed path.

CI at this head, the reviewer's reading: 33 names: 27 success, 3 skipped, and ⚠️ 3 in_progress (Test Core (1/6), Test Core (5/6), Type Check · workspace). NOT MEASURED, never a pass. 0 failure.

Non-blocking findings, and what the seat does with them

  1. ⚠️ The three groups are not a partition, and one gap sits in the by-hand instruction. Two carriers are measured outside the groups:
    • A flow CRUD node's config.filter (a z.record under a walked key) parses GREEN and is lint-refused. It behaves like group (2) and names no group: harmless for a sweep.
    • A lookup field's lookupFilters parses GREEN AND lints GREEN, on both trees. It is refused by neither door, like group (3). But acceptanceCriteria names only interfaceConfig.filterBy as the by-hand sweep, so an upgrader who runs the lint and searches filterBy misses it. That is exactly the failure this card exists to remove.
      ⇒ A small round 3, the same call as fix(spec): correct both $between-endpoint entries' carrier claims and detectors #19752's NB-2: name lookupFilters in group (3) and in the by-hand clause, name the flow config.filter shape in group (2), and state the groups as the carriers measured rather than as a closed partition. A new head owes its own record.
  2. The field-typed arm refuses equality only where the lint can bind the filter to an object. The sentence carries no universal and stands. Round 3 may tighten it to 「a field the lint can resolve」.
  3. 「only a search … finds it」 is scoped by 「at publish」. The engine refuses at query time. It stands.

Implemented-by: claude/issue-19778-preset-entry-carriers
Reviewed-by: session_013RDBh5DqXd2xnLwvHLgLFr

VERDICT: PASS


Generated by Claude Code

…rrier

A lookup field's lookupFilters is walked by neither door, like a page's
interfaceConfig.filterBy, and a flow CRUD node's loose-record config.filter
is refused by the lint alone. Both now sit in the surface's groups, the
by-hand clause covers the gt / gte / lt / lte spelling lookupFilters takes,
the groups are stated as the carriers measured rather than a closed
partition, and the field-typed arm names the binding it needs.

Claude-Session: https://claude.ai/code/session_013RDBh5DqXd2xnLwvHLgLFr
Co-authored-by: Claude <noreply@anthropic.com>
…r groups

Output of `pnpm --filter @objectstack/spec gen:migration-registry`, never
hand-edited; every changed line is a string literal inside step 18's
filter-preset-ordering-comparand-refused entry.

Claude-Session: https://claude.ai/code/session_013RDBh5DqXd2xnLwvHLgLFr
Co-authored-by: Claude <noreply@anthropic.com>

Copy link
Copy Markdown
Collaborator

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: a82423396b1c204e2d8f6a1ae76377d9d20ea736

Card #19778, round 3. Base main, merge-base 2cf9db7c43. Round-3 range 775de57ad9..a82423396b: 3 commits, 3 files. The round-2 PASS (5790286599) covered the whole diff at 775de57ad9; this record binds the new head. Reading moment: 2026-09-23T07:36Z. A record binds ONE head and one reading moment; it is ⛔ not a standing statement about CI.

Tier, measured by the SEAT from the reviewer's transcript (⛔ not its self-report): CONTRACT_REVIEW_TIER on origin/main is claude-fable-5-1. Governing assistant-request stamps 99 / 99 claude-fable-5-1; superset 217 / 217; dark control empty. The fallback/overload sweep, read in context, is quoted earlier records and repo source. ⛔ None of it is a notice. ⇒ AT TIER. This seat is off-tier and ⛔ did not 自审.

① Derived judgments

The round-2 blocking concern is resolved: lookupFilters is now named in group (3) and in the by-hand sweep. Every round-3 sentence holds on its literal reading on this head AND on #19750's current head 07d787ce96. One probe of 134 lines was run against each tree's own dist, and the two outputs differ only on the tree-control line. Every dark control is GREEN on both doors.

  • Group (1). 8 slots × $gt/$between: parse-REFUSED at the comparand's own path AND lint-REFUSED at the located path.
  • Group (2). These parse GREEN and are lint-REFUSED:
    • ListViewSchema.filter, a page element's dataSource.filter, and the record:related_list filter prop (a triple too);
    • the flow get_record / update_record / delete_record config.filter (a loose record).
  • Group (3). interfaceConfig.filterBy and FieldSchema.lookupFilters parse GREEN (through ObjectSchema too) and lint GREEN. The whole authoring rule set, under both lint and validate, reports 0 findings naming either key. Controls: a malformed filterBy value is refused, so the slot is parsed. The identical rule under a walked key is refused, so the key name is what decides.
  • The lookupFilters operator claim. The source enum is eq/ne/gt/lt/gte/lte/contains/in/notIn. Measured: those nine are GREEN; 18 other spellings are refused at lookupFilters.0.operator, including between, greater_than, $gt and >. ⇒ the only ordering spellings are gt/gte/lt/lte, and there is no between.
  • The tightened reason arm. The schema door judges no equality or membership. The lint's field-typed arm refuses on a bound date or datetime field. It is GREEN where the dataset resolves to nothing, where the field is undeclared, and on select, time and an API-provider view. An unbound ordering comparand is still refused, because arm 1 is field-agnostic.
  • 「Carriers measured, not a closed partition」. The reviewer searched every FilterConditionSchema slot in non-test packages/spec/src for an authored carrier under a key the lint does not walk and found no counterexample.
  • Nothing else moved. The key set, id, the ruling citation and the no-D2 sentence are byte-identical. No tracker number was added, nothing names defaultFilters or a refinement function, and packages/lint is untouched.

② Semver level

patch on @objectstack/spec is right. Across the four shipped bundles, the 5 removed strings go 4 → 0 and the 9 new strings go 0 → 4. The dark control holds at 4, and the zero control holds at 0. The changeset's cumulative claims were re-measured from a base build: the round-2 removals go 4 → 0, and the nine unique sentences go 0 → 4. The registry is byte-exact generator output: the regenerated blob is unchanged, check:migration-registry exits 0 (232 / 207 / 183), and all three hunks are string literals inside this entry.

③ Boundary flags

None tripped. Three files; Fixes #19778; line-leading Clause-②: no. check-commit-card-trailers exits 0 over all six commits. No governed path.

CI at this head, read after the seat's 07:20Z body edit: 35 names — 30 success, 5 skipped, 0 in progress / queued / cancelled / failure. All seven required contexts are success. The five skips are path- or event-gated, and none is required.

Non-blocking, recorded and ⛔ not a round:

  • The changeset says the lint's filter walk 「descends neither key」. The walk does recurse through them; what it never does is hand either subtree to the visitor. The behaviour stated is right and only the verb is loose. The shipped entry itself says 「does NOT walk」, which is accurate.
  • The PR body's CI line is the pre-edit reading. It is body-only and does not ship.
  • The body carries two footers, because the platform appended one on the seat's edit.

Implemented-by: claude/issue-19778-preset-entry-carriers
Reviewed-by: session_013RDBh5DqXd2xnLwvHLgLFr

VERDICT: PASS


Generated by Claude Code

@os-support-ai
os-support-ai marked this pull request as ready for review September 23, 2026 07:37
@os-support-ai
os-support-ai added this pull request to the merge queue Sep 23, 2026
Merged via the queue into main with commit 9df3934 Sep 23, 2026
44 checks passed
@os-support-ai
os-support-ai deleted the claude/issue-19778-preset-entry-carriers branch September 23, 2026 08:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tooling

Projects

None yet

2 participants